import * as THREE from '../../../../modules/three/three.module.js'
import {flatten} from "../util/StaticMethods.js"
import SimpleMarkEntity from "./SimpleMarkEntity.js";
import {IsoTolerance} from "../../model/IsoLineVO.js";

const origin = new THREE.Vector3(0, 0, 0)
const unitZ = new THREE.Vector3(0, 0, 1)
const defaultScale = 0.35
const minScale = 0.1
const scaleStep = 0.55
const colorDefault = '#0d53f1'
const colorActive = '#009d32'
const size = 0.125
const octGeometry = new THREE.TetrahedronGeometry(size) // OctahedronGeometry
const quat = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(1, 1, 1).normalize(), new THREE.Vector3(0, 0, -1))
const mat = new THREE.Matrix4().makeRotationFromQuaternion(quat)
mat.setPosition(0, 0, size)
octGeometry.applyMatrix4(mat)
const octMtl = new THREE.MeshBasicMaterial({color: colorDefault, wireframe: true})

function clampDegree0_360(degree) {
  let clamped = degree % 360
  if (clamped < 0) {
    return clamped + 360
  }
  return clamped
}
function clampDegreePosiOrNeg180(degree) {
  const deg = clampDegree0_360(degree)
  return deg > 180 ? deg - 360 : deg
}
const txtOption = SimpleMarkEntity.getTxtOption()
txtOption.fontSize = 0.5

export default class NormalPointIndicator extends THREE.Mesh {
  /**
   * @type {NormalPointVO}
   */
  vo
  bottomMesh
  /**
   * @type {SimpleMarkEntity}
   */
  tip

  constructor(vo) {
    super()
    const radius = 1
    const height = 1

    const pts = [origin];
    const faceIndices = [];
    const lineIndices = [];
    const segment = 7;
    const angle = (Math.PI * 2) / segment;
    for (let i = 0; i < segment; i++) {
      const a = angle * i;
      const x = radius * Math.sin(a);
      const y = radius * Math.cos(a);
      const pt = new THREE.Vector3(x, y, 0);
      pts.push(pt);
      if (i === segment - 1) {
        lineIndices.push(1, segment);
        faceIndices.push(0, 1, segment);
      } else {
        lineIndices.push(i + 1, i + 2);
        faceIndices.push(0, i + 2, i + 1);
      }
    }

    pts.push(new THREE.Vector3(0, 0, height));
    lineIndices.push(0, segment + 1);

    const geometry = new THREE.BufferGeometry()
    geometry.setIndex(faceIndices)
    const posAttr = new THREE.BufferAttribute(flatten(pts), 3)
    geometry.setAttribute('position', posAttr)
    const mtl = new THREE.MeshBasicMaterial({color: colorDefault, opacity: 0.4, transparent: true, wireframe: false, toneMapped: false})
    const bottomMesh = new THREE.Mesh(geometry, mtl)
    this.add(bottomMesh)
    this.bottomMesh = bottomMesh
    this.geometry = octGeometry
    this.material = octMtl

    const lineGeo = new THREE.BufferGeometry();
    lineGeo.setIndex(lineIndices);
    lineGeo.setAttribute('position', posAttr);
    const lineSegments = new THREE.LineSegments(lineGeo, mtl);
    bottomMesh.add(lineSegments);
    bottomMesh.scale.set(defaultScale, defaultScale, 1)

    this.vo = vo
    this.syncToVO()
  }

  toggleSelect(selected) {
    this.bottomMesh.material.opacity = selected ? 0.9 : 0.4;
    // this.material.wireframe = !selected
  }
  toggleActive(active, camera) {
    const clr = active ? colorActive : colorDefault
    this.bottomMesh.material.color.setStyle(clr)
    if (active) {
      if (camera) {
        this.showTip(this.parent, camera)
      }
      this.updateTip()
    } else {
      this.hideTip()
    }
  }
  toggleAdhered(adhered) {
    this.bottomMesh.material.color.setStyle(adhered ? '#f60' : colorDefault)
    // this.material.wireframe = !selected
  }
  bottomExpand(scaleVal = 0) {
    const scale = scaleVal || this.bottomMesh.scale.x + scaleStep
    this.bottomMesh.scale.set(scale, scale, 1)
    console.log('bottomExpand: ', scale)
  }
  bottomShrink(scaleVal = 0) {
    let scale = scaleVal || this.bottomMesh.scale.x - scaleStep
    if (scale < minScale) {
      scale = minScale
    }
    this.bottomMesh.scale.set(scale, scale, 1)
  }
  scaleReset() {
    this.bottomMesh.scale.set(defaultScale, defaultScale, 1)
  }
  showTip(scene, camera) {
    if (!this.tip) {
      this.tip = new SimpleMarkEntity('', txtOption).setEye(camera)
    }
    scene.add(this.tip)
  }
  updateTip() {
    if (!this.tip) {
      return
    }
    this.tip.position.copy(this.vo.position).add(this.vo.normal)
    let [degPhi, degTheta] = this.vo.getDegrees()
    degTheta = clampDegreePosiOrNeg180(degTheta)
    const text = `Θ: ${degPhi.toFixed(2)}, φ: ${degTheta.toFixed(2)}`
    if (this.tip.text !== text) {
      this.tip.text = text
      this.tip._txtMesh.colorRanges = null
      this.tip.updateTextAndSize();
    }
  }
  hideTip() {
    if (!this.tip) {
      return
    }
    this.tip.removeFromParent()
  }

  /**
   * @param {IsoLineEntity} isoLine 等值线
   */
  updateTipByColor(isoLine) {
    if (!this.tip) {
      return
    }
    const isoLineVO = isoLine.vo
    if (!isoLine.points.length) {
      return
    }
    this.tip.position.copy(this.vo.position).add(this.vo.normal)
    // 期望间隔，小于等于零时，忽略这个参数
    const expectedSpan = isoLine.getExpectedSpan()
    let dThetaOrPhi = 0
    let dSpan = 0
    const dLabel = isoLineVO.isTheta() ? 'dΘ' : 'dφ'
    const refPoint = isoLine.points[0].vo
    const [degPhi0, degTheta0] = refPoint.getDegrees()
    const [degPhi, degTheta] = this.vo.getDegrees()
    let colorLabel = false
    if (isoLineVO.isTheta()) {
      dThetaOrPhi = degTheta - degTheta0
      dThetaOrPhi = clampDegreePosiOrNeg180(dThetaOrPhi)
      colorLabel = Math.abs(dThetaOrPhi) > IsoTolerance.theta
      dSpan = degPhi - degPhi0
    } else {
      dThetaOrPhi = degPhi - degPhi0
      colorLabel = Math.abs(dThetaOrPhi) > IsoTolerance.phi
      dSpan = degTheta - degTheta0
      dSpan = clampDegreePosiOrNeg180(dSpan)
    }
    let text = `${dLabel}: ${dThetaOrPhi.toFixed(2)}`
    let colorSpan = false
    if (expectedSpan > 0) {
      colorSpan = Math.abs(Math.abs(dSpan) - expectedSpan) > IsoTolerance.span
      text += `, Δ: ${dSpan.toFixed(2)}`
    }
    if (colorLabel || colorSpan) {
      const keys = []
      const colorRanges = text.split('').reduce((out, char, i) => {
        if (i === 0 || /\s/.test(char)) {
          out[i] = '#0b0'
          keys.push(i)
        }
        return out
      }, {})
      if (colorLabel) {
        colorRanges[keys[0]] = '#d00'
        colorRanges[keys[1]] = '#d00'
      }
      if (colorSpan) {
        colorRanges[keys[2]] = '#aa0'
        colorRanges[keys[3]] = '#aa0'
      }
      this.tip._txtMesh.colorRanges = colorRanges
    } else {
      this.tip._txtMesh.colorRanges = null
    }
    this.tip.text = text
    this.tip.updateTextAndSize();
  }

  updateVO() {
    this.vo.position.copy(this.position)
    this.vo.normal.copy(unitZ).applyQuaternion(this.quaternion)
  }
  syncToVO() {
    this.position.copy(this.vo.position)
    this.quaternion.setFromUnitVectors(unitZ, this.vo.normal)
  }
}
